#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""Boo language utility functions."""

namespace Grawlix.Meta
import System
import Boo.Lang.Compiler
import Boo.Lang.Compiler.TypeSystem

def markNamespaceUsed(module as Ast.Module, fullNamespace as string):
"""
Mark a specific namespace as used so it doesn't get warnings.
Param module: The module to mark.
Param fullNamespace: The name of the namespace.
"""
	for imp in module.Imports:
		if imp.Namespace == fullNamespace:
			imp.NamespaceUsed = true

def enclosingType(child as Ast.Node):
"""
Find the enclosing type of the specified node.
Param child: The node whose parent type to find.
Returns: The enclosing type.
"""
	child = child.ParentNode
	while child is not null:
		typed = child as Ast.TypeDefinition
		if typed is not null:
			return typed
		child = child.ParentNode
	return null

def enclosingModule(child as Ast.Node):
"""
Find the enclosing module of the specified node.
Param child: The node whose parent module to find.
Returns: The enclosing module.
"""
	child = child.ParentNode
	while child is not null:
		typed = child as Ast.Module
		if typed is not null:
			return typed
		child = child.ParentNode
	return null

def replaceIdentifier(
	node as Ast.Node,
	original as string,
	replacement as string):
"""
Replace the identifier with 
Param node: The node to recurse down.
Param original: The identifier to replace.
Param replacement: The new name for the identifier.
"""
	IdentifierReplacer(original, replacement).Apply(node)

def returnsValue([required] method as Ast.Method):
"""
Check a method to see if there is a return type or return statement.
Param method: The method to check.
Returns: true if there is a return value, else false.
"""
	# Will not work with macros that generate return statements
	finder = ReturnExpressionFinder()
	finder.Apply(method)
	return finder.HasReturnStatements or finder.HasYieldStatements

def returnsValue(
	[required] method as IMethod,
	[required] typeSystemServices as TypeSystemServices):
"""
Check a method to see if there is a return type or return statement.
Param method: The method to check.
Param typeSystemServices: The TypeSystemServices
Returns: true if there is a return value, else false.
"""
	if \
		method.ReturnType is null or \
		TypeSystemServices.IsUnknown(method.ReturnType):
		genericMethod = method as GenericConstructedMethod
		if genericMethod is not null:
			method = genericMethod.GenericDefinition
		internalMethod = method as InternalMethod
		assert internalMethod is not null
		return returnsValue(internalMethod.Method)
	else:
		return typeSystemServices.VoidType == method.ReturnType

def createDeferredInvocation(
	[required] declaration as Ast.Method,
	[required] deferredMethodName as string):
"""
Create a deferred invocation upon self or statically.
Param declaration: The method declaration to defer.
	The invocation is built to be used within this declaration.
Param deferredMethodName: The name of the method to call.
Returns: The deferred invocation expression.
Remarks: Does not generate entities for the expression.
"""
	targetMethod = \
		Ast.ReferenceExpression(declaration.LexicalInfo, deferredMethodName)
	return createDeferredInvocation(declaration, targetMethod)

def createDeferredInvocation(
	[required] declaration as Ast.CallableDefinition,
	[required] callTarget as Ast.Expression,
	[required] deferredMethodName as string):
"""
Create a deferred invocation upon a specified object.
Param declaration: The method declaration to defer.
	The invocation is built to be used within this declaration.
Param callTarget: The object to call the method upon.
Param deferredMethodName: The name of the method to call.
Returns: The deferred invocation expression.
Remarks: Does not generate entities for the expression.
"""
	targetMethod = \
		Ast.MemberReferenceExpression(
			declaration.LexicalInfo,
			callTarget,
			deferredMethodName)
	return createDeferredInvocation(declaration, targetMethod)

def createDeferredInvocation(
	[required] declaration as Ast.Method,
	[required] targetMethod as Ast.Expression):
"""
Create a deferred invocation upon a specified method.
Param declaration: The method declaration to defer.
	The invocation is built to be used within this declaration.
Param targetMethod: The method expression to call.
Returns: The deferred invocation expression.
Remarks: Does not generate entities for the expression.
"""
	invocation = Ast.MethodInvocationExpression(declaration.LexicalInfo)
	invocation.IsSynthetic = true
	invocation.Target = targetMethod
	for parameter in declaration.Parameters:
		argument = \
			Ast.ReferenceExpression(parameter.LexicalInfo, parameter.Name)
		invocation.Arguments.Add(argument)
	return invocation

private class IdentifierReplacer(Ast.DepthFirstVisitor):
"""Replace identifiers starting a specified node."""
	
	def constructor(original as string, replacement as string):
	"""
	Initializes a new visitor.
	Param original: The original identifier.
	Param replacement: The identifier to replace with.
	"""
		_original = original
		_replacement = replacement
	
	def Apply(node as Ast.Node):
	"""
	Start the replacement.
	Param node: The root of the search.
	"""
		Visit(node)
	
	override def OnReferenceExpression(node as Ast.ReferenceExpression):
	"""
	Act upon a reference expression.
	Param node: The expression.
	"""
		if node.Name == _original:
			node.Name = _replacement
	
	private _original as string
	"""The original identifier."""
	
	private _replacement as string
	"""The identifier to replace with."""
	
	
	